package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.Jsons;
import com.atguigu.gmall.gateway.properties.AuthUrlProperties;
import com.atguigu.gmall.user.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;


/**
 * 网关的全局过滤器。每个请求过来过滤器都会启动拦截。和HttpFilter
 *
 * 统一鉴权 + 用户id透传
 */
@Slf4j
@Component
public class UserAuthFilter implements GlobalFilter {


    @Autowired
    AuthUrlProperties urlProperties;

    AntPathMatcher matcher = new AntPathMatcher();

    @Autowired
    StringRedisTemplate redisTemplate;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        String path = exchange.getRequest().getURI().getPath();
        log.info("请求开始：请求路径:{}",path);

        // 无需认证 如果请求静态资源，直接放行
        List<String> noauthurl = urlProperties.getNoauthurl();
        for (String pattern : noauthurl) {
            // 判断当前请求是否满足这种路径格式
            if (matcher.match(pattern, path)) {
                return chain.filter(exchange);
            }
        }

        // 【任何情况浏览器都不能访问的 /api/inner/**】
        for (String pattern : urlProperties.getDenyurl()) {
            if(matcher.match(pattern,path)){
                //直接拒绝。响应一个json即可
                Result<String> result = Result.build("", ResultCodeEnum.PERMISSION);
                return responseJson(exchange,result);
            }
        }

        // 需要登录 如果请求路径是需要登录才能访问。验证登录
        for (String pattern : urlProperties.getLoginauth()){
            if (matcher.match(pattern,path)){
                // 先拿到用户的登录后请求传来的 token
                String token = getUserToken(exchange);
                if (!StringUtils.isEmpty(token)){
                    // 说明用户登录了
                    // 令牌真伪校验】是真令牌还是假令牌
                    UserInfo info = getUserInfoByToken(token);
                    if (info == null){
                        // 假令牌，伪造登录
                        return locationToUrl(exchange,urlProperties.getLoginPage());
                    }
                    // 真令牌直接放行  透传用户id【给request中加一个userid的请求头】再放行
                    return userIdThrought(chain,exchange,info);
                }else {
                    // 说明用户没登录，网关直接把请求打回到登录页
                    return locationToUrl(exchange,urlProperties.getLoginPage());
                }
            }
        }

        //========满足特定规则的请求==============

        //4、普通请求。【用户没带token 拉倒。如果带了，就透传id】
        String userToken = getUserToken(exchange);
        if(!com.alibaba.nacos.common.utils.StringUtils.isEmpty(userToken)){
            //带了令牌。顺便验证下令牌对不对
            UserInfo userInfo = getUserInfoByToken(userToken);
            if(userInfo == null){
                //令牌错误
                return locationToUrl(exchange,urlProperties.getLoginPage());
            }else {
                //令牌正确。
                return userIdThrought(chain,exchange,userInfo);
            }
        }else {
            //不带令牌的不用验证。也需要把临时id透传下去
            return userTempIdThrought(chain,exchange);
        }
    }

    private Mono<Void> userTempIdThrought(GatewayFilterChain chain, ServerWebExchange exchange) {
        String tempId = getUserTempId(exchange);
        //1、准备一个新请求对象
        ServerHttpRequest newRequest = exchange.getRequest()
                .mutate()
                .header(RedisConst.USER_TEMP_ID_HEADER,tempId)
                .build();

        //2、准备一个新exchange对象
        ServerWebExchange webExchange = exchange.mutate()
                .request(newRequest)  //请求
                .response(exchange.getResponse()) //响应
                .build();

        return chain.filter(webExchange);
    }

    private Mono<Void> responseJson(ServerWebExchange exchange,
                                    Result<String> result) {
        String jsonStr = Jsons.toStr(result);

        //1、拿到响应写数据出去
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);

        // 2、创建要写出去的数据
        DataBuffer dataBuffer = response.bufferFactory().wrap(jsonStr.getBytes(StandardCharsets.UTF_8));

        //3、写出去
        return response.writeWith(Mono.just(dataBuffer));
    }

    private Mono<Void> userIdThrought(GatewayFilterChain chain, ServerWebExchange exchange, UserInfo info) {
        Long userId = info.getId();

        //放行之前给请求头中加一个用户id
        //默认请求头是只读的
//        exchange.getRequest()
//                .getHeaders()
//                .add(RedisConst.USER_ID_HEADER,userId+"");
        //响应式编程中怎么修改请求头
        String tempId = getUserTempId(exchange);
        //1、准备一个新请求对象
        ServerHttpRequest newRequest = exchange.getRequest()
                .mutate()
                .header(RedisConst.USER_ID_HEADER, userId.toString())
                .header(RedisConst.USER_TEMP_ID_HEADER,tempId)
                .build();

        //2、准备一个新exchange对象
        ServerWebExchange webExchange = exchange.mutate()
                .request(newRequest)  //请求
                .response(exchange.getResponse()) //响应
                .build();

        return chain.filter(webExchange);
    }

    private String getUserTempId(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        String tempId = "";
        //1、尝试从请求头中 cookie字段，获取值
        HttpCookie cookie = request.getCookies().getFirst("userTempId");
        if (cookie != null) {
            tempId = cookie.getValue();
        } else {
            //2、尝试从请求头中 token 字段
            tempId = request.getHeaders().getFirst("userTempId");
        }
        return tempId;
    }

    private UserInfo getUserInfoByToken(String token) {
        String json = redisTemplate.opsForValue().get(RedisConst.USER_LOGIN + token);
        if (StringUtils.isEmpty(json)){
            UserInfo info = Jsons.toObj(json, UserInfo.class);
            return info;
        }

        return null;
    }

    // 跳转到指定页面
    private Mono<Void> locationToUrl(ServerWebExchange exchange, String page) {
        // 重定向到指定页
        ServerHttpResponse response = exchange.getResponse();
        String path = exchange.getRequest().getURI().toString();

        // 重定向： Location: http://www.baidu.com
        // 响应状态码 302
        response.setStatusCode(HttpStatus.FOUND);
        response.getHeaders().add("Location",page+"?originUrl="+path);

        return response.setComplete();
    }

    private String getUserToken(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        String userToken = "";
        // 尝试从请求头中 cookie字段，获取值
        HttpCookie cookie = request.getCookies().getFirst("token");
        if (cookie != null) {
            userToken = cookie.getValue();
        } else {
            // 尝试从请求头中 token 字段
            userToken = request.getHeaders().getFirst("token");
        }

        return userToken;
    }
}
